module dgl.graphics.ModelMesh;

import tango.io.Console;
import tango.text.convert.Integer;
import tango.text.Util;

import derelict.opengl.gl;
import derelict.opengl.glu;

import dgl.Vector3;
import dgl.Vector4;
import dgl.GameTime;
import dgl.Matrix;

import dgl.content.ContentManager;
import dgl.graphics.VertexData;
import dgl.BoundingBox;
import dgl.BoundingSphere;
import dgl.graphics.FaceData;
import dgl.graphics.IDrawable;
import dgl.graphics.IUpdateable;
import dgl.graphics.LoadHelper;
import dgl.graphics.Model;
import dgl.graphics.MeshContainer;
import dgl.graphics.ShaderProgram;
import dgl.graphics.Texture2D;

class ModelMesh : IDrawable
{	
	private VertexData[] m_vertices;
	
	private FaceData[] m_faces;
	
	private GLuint m_vb;
	
	private GLuint m_ib;
	
	private BoundingSphere m_boundingSphere;
	
	private BoundingBox m_aabb;
	
	private ShaderProgram m_shader;
	
	/// Should be in FaceData. Put it here to test
	private bool m_hasFaceUV;
	
	/// Should be in FaceData. Put it here to test
	private Texture2D m_texture;
	
	public BoundingSphere boundingSphere()
	{
		return m_boundingSphere;
	}
	
	public BoundingBox aabb()
	{
		return m_aabb;
	}
	
	public ShaderProgram shader()
	{
		return m_shader;
	}
	
	public void shader( ShaderProgram shader )
	{
		m_shader = shader;
	}
	
	public static ModelMesh loadFromFile( LoadHelper loadHelper, MeshContainer meshContainer, bool hasFaceUV )
	{
		char[][] data;
		ModelMesh ret = new ModelMesh();
		
		ret.m_hasFaceUV = hasFaceUV;

		VertexData[] vertices = loadVertices( loadHelper, hasFaceUV );
		FaceData[] faces = loadFaces( loadHelper, ret );
		
		ret.createBuffers( vertices, faces );
		ret.calcBoundingSphere();
		ret.calcAABB();

		return ret;
	}

	private static VertexData[] loadVertices( LoadHelper loadHelper, bool hasFaceUV )
	{
		char[] data;
		
		uint vertexCount = 0;
		if( loadHelper.readLine( data ) == "\tVertices" )
		{
			vertexCount = toInt( data );
		}
		VertexData[] ret = new VertexData[vertexCount];
		
		for( int i = 0; i < vertexCount; i++ )
		{
			if( loadHelper.readLine( data ) == "\t\tIndex" )
			{
				ret[i].index = toInt( data );
			}
			if( loadHelper.readLine( data ) == "\t\tPosition" )
			{
				ret[i].position = LoadHelper.stringsToVector3( data );
			}
			if( loadHelper.readLine( data ) == "\t\tNormal" )
			{
				ret[i].normal = LoadHelper.stringsToVector3( data );
			}
			if( loadHelper.readLine( data ) == "\t\tColor" )
			{
				ret[i].color = LoadHelper.stringsToVector4( data ) / 255f;
			}
			if( hasFaceUV )
			{
				if( loadHelper.readLine( data ) == "\t\tTexCoord" )
				{
					ret[i].texcoord = LoadHelper.stringsToVector2( data );
					ret[i].texcoord.y = 1 - ret[i].texcoord.y;
				}
			}
		}
		
		return ret;
	}

	private static FaceData[] loadFaces( LoadHelper loadHelper, ModelMesh modelMesh )
	{
		char[] data;
		
		uint faceCount = 0;
		if( loadHelper.readLine( data ) == "\tFaces" )
		{
			faceCount = toInt( data );
		}
		FaceData[] ret = new FaceData[faceCount];
		
		for( int i = 0; i < faceCount; i++ )
		{
			if( loadHelper.readLine( data ) == "\t\tIndex" )
			{
				ret[i].index = toInt( data );
			}
			bool hasTexture = false;
			if( loadHelper.readLine( data ) == "\t\tHasTexture" )
			{
				hasTexture = cast(bool)( toInt( data ) );
			}
			if( loadHelper.readLine( data ) == "\t\tVertexIndex" )
			{
				char[][] elements = split( data, " " );
				for( int j = 0; j < elements.length; j++ )
				{
					ret[i].vertices[j] = toInt( elements[j] );
				}
			}
			if( hasTexture )
			{
				if( loadHelper.readLine( data ) == "\t\tTexture" )
				{
					ret[i].texture = ContentManager.load!(Texture2D)( data );
					modelMesh.m_texture = ret[i].texture;
				}
			}
		}
		
		return ret;
	}

	private void createBuffers( VertexData[] vertices, FaceData[] faces )
	{
		m_vertices = vertices;
		m_faces = faces;
		
		GLuint[] indices = new GLuint[faces.length * 3];
		
		int index = 0;
		for( int i = 0; i < faces.length; i++ )
		{
			for( int j = 0; j < 3; j++ )
			{
				indices[index] = faces[i].vertices[j];
				index++;
			}
		}
		
		glGenBuffers( 1, &m_vb );
		glBindBuffer( GL_ARRAY_BUFFER, m_vb );
		glBufferData( GL_ARRAY_BUFFER, vertices.length * VertexData.sizeof, vertices.ptr, GL_STATIC_DRAW );
		
		glGenBuffers( 1, &m_ib );
		glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_ib );
		glBufferData( GL_ELEMENT_ARRAY_BUFFER, indices.length * GLuint.sizeof, indices.ptr, GL_STATIC_DRAW );
	}
	
	private void calcBoundingSphere()
	{
		float maxDistance = 0;
		Vector3 vec1, vec2;
		foreach( v1; m_vertices )
		{
			foreach( v2; m_vertices )
			{
				float dist = Vector3.distance( v1.position, v2.position );
				if( dist > maxDistance )
				{
					maxDistance = dist;
					vec1 = v1.position;
					vec2 = v2.position;
				}
			}
		}
		Vector3 center = ( vec1 + vec2 ) / 2;
		m_boundingSphere = BoundingSphere( center, maxDistance / 2 );
	}
	
	private void calcAABB()
	{
		Vector3 min = Vector3( float.max, float.max, float.max );
		Vector3 max = Vector3( -float.max, -float.max, -float.max );
		
		foreach( vertex; m_vertices )
		{
			Vector3 pos = vertex.position;
			if( pos.x > max.x )
				max.x = pos.x;
			if( pos.y > max.y )
				max.y = pos.y;
			if( pos.z > max.z )
				max.z = pos.z;
			if( pos.x < min.x )
				min.x = pos.x;
			if( pos.y < min.y )
				min.y = pos.y;
			if( pos.z < min.z )
				min.z = pos.z;
		}

		m_aabb = BoundingBox( min, max );
	}
	
	public void draw( GameTime gameTime )
	{
		glEnableClientState( GL_VERTEX_ARRAY );
		glEnableClientState( GL_COLOR_ARRAY );
		glEnableClientState( GL_NORMAL_ARRAY );
		if( m_hasFaceUV )
		{
			glEnableClientState( GL_TEXTURE_COORD_ARRAY );
		}
		
		glBindBuffer( GL_ARRAY_BUFFER, m_vb );
		glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_ib );
		
		int curSize = uint.sizeof;
		glVertexPointer( 3, GL_FLOAT, VertexData.sizeof, cast(void*)curSize );
		curSize += Vector3.sizeof;
		glColorPointer( 4, GL_FLOAT, VertexData.sizeof, cast(void*)curSize );
		curSize += Vector4.sizeof;
		glNormalPointer( GL_FLOAT, VertexData.sizeof, cast(void*)curSize );
		if( m_hasFaceUV )
		{
			curSize += Vector3.sizeof;
			glTexCoordPointer( 2, GL_FLOAT, VertexData.sizeof, cast(void*)curSize );
		}
		/*
		if( m_texture )
		{
			glBindTexture( GL_TEXTURE_2D, m_texture.id );
		}
		else
		{
			glBindTexture( GL_TEXTURE_2D, 0 );
		}
		*/
		if( m_shader )
		{
			glUseProgram( m_shader.id );
		}
		else
		{
			glUseProgram( 0 );
		}
		
		glDrawElements( GL_TRIANGLES, m_faces.length * 3, GL_UNSIGNED_INT, null );
	}
}